﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TVGuide.DataAccess.Exceptions;
using System.Data.Objects;
using TVGuide.Common;

namespace TVGuide.DataAccess
{
    sealed class DataAccessor
    {
        private static tvchanneldbEntities dataModel;
        private static DataAccessor instance;

        private DataAccessor()
        {
            dataModel = new tvchanneldbEntities();
        }

        /// <summary>
        ///     Get the instance of singleton tvchanneldbEntities
        /// </summary>
        /// <returns>Returns the instance of tvchanneldbEntities</returns>
        public static DataAccessor getInstance()
        {
            if (instance == null)
            {
                instance = new DataAccessor();
            }
            return instance;
        }
        
        /// <summary>
        ///     Adds channel into channels table of the database
        /// </summary>
        /// <param name="ch"> Channel object to be added</param>
        /// <returns>Returns the obtained id of the channel</returns>
        public int AddChannel(channel ch)
        {
            if (ch == null)
                throw new InvalidArgsException("The parameter is null");

            if (String.IsNullOrEmpty(ch.channelName))
                throw new InvalidArgsException("The 'channelName'property is null");

            if (ch.date == null)
                throw new InvalidArgsException("The 'date'property is null");

            int count = dataModel.channels.Count();

            try
            {
                if (dataModel.languages.FirstOrDefault(o => o.languageID == ch.languageID) == null)
                    throw new InvalidArgsException("Invalid 'languageId'");
            }
            catch(InvalidOperationException)
            {
                throw new InvalidArgsException("Invalid 'languageId'");
            }
            
            if (count > 0 && dataModel.channels.FirstOrDefault(o => o.channelName.Equals(ch.channelName)) != null)
                throw new InvalidArgsException("This channel already exsists");

            // No channel Id provided, we must provide
            if (ch.channelID == 0)
            {
                if (count == 0)
                    ch.channelID = 1;
                else
                    ch.channelID = (short)((from channel in dataModel.channels select channel.channelID).Max() + 1);
            }
            // Channel Id provided
            else             
            { 
                if(dataModel.channels.FirstOrDefault(o => o.channelID == ch.channelID) != null)
                    throw new InvalidArgsException("Invalid id, this id already exsists");
            }

            dataModel.channels.AddObject(ch);
            dataModel.SaveChanges();
            return ch.channelID;
        }

        /// <summary>
        ///     Returns channel name for given ID
        /// </summary>
        /// <param name="id">Int id of the channel</param>
        /// <returns>Returns string nameof the channel</returns>
        public string GetChannelName(int id)
        {
            return dataModel.channels.FirstOrDefault(o => o.channelID == id).channelName;
        }
        
        /// <summary>
        ///     Returns channel by id
        /// </summary>
        /// <param name="id">Int id of the channel</param>
        /// <returns>Channel corresponding to given id</returns>
        public channel GetChannelById(int id)
        {
            return dataModel.channels.FirstOrDefault(o => o.channelID == id);
        }

        /// <summary>
        ///     Adds program into programs table of the database
        /// </summary>
        /// <param name="commitChange">True to commit the change</param>
        /// <param name="pr">Program object to be added</param>
        public void AddProgram(program pr, bool commitChange)
        {
            if (pr == null)
                throw new InvalidArgsException("The parameter is null");

            try
            {
                if (dataModel.channels.FirstOrDefault(o => o.channelID == pr.channelID) == null)
                    throw new InvalidArgsException("Invalid channelId");
            }
            catch(InvalidOperationException)
            {
                throw new InvalidArgsException("Invalid channelId");
            }

            DateTime dt = new DateTime(2000, 01, 01);

            if (pr.date < dt)
                throw new InvalidArgsException("The 'date'property is null");

            if (String.IsNullOrEmpty(pr.title))
                throw new InvalidArgsException("The 'title' property is null or empty");

            if (pr.countryID != null)
            {
                try
                {
                    if (dataModel.countries.FirstOrDefault(o => o.countryID == pr.countryID) == null)
                        throw new InvalidArgsException("Invalid countryId");
                }
                catch (InvalidOperationException)
                {
                    throw new InvalidArgsException("Invalid countryId");
                }
            }

            if (pr.categoryID != null)
            {
                try
                {
                    if (dataModel.categories.FirstOrDefault(o => o.catID == pr.categoryID) == null)
                        throw new InvalidArgsException("Invalid categoryId");
                }
                catch (InvalidOperationException)
                {
                    throw new InvalidArgsException("Invalid categoryId");
                }
            }

            var program = dataModel.programs.FirstOrDefault(o => (o.channelID == pr.channelID) && 
                                                                 (o.languageID == pr.languageID) &&
                                                                 (o.date == pr.date));
            // The case if the same program for the same channel at the same time is entered
            // update the existing record
            if (program != null)
            {
                program.title = pr.title;
                program.categoryID = pr.categoryID;
            }
            else
            {
                dataModel.programs.AddObject(pr);
            }
            if(commitChange)
                dataModel.SaveChanges();
        }

        /// <summary>
        ///     Adds program into programs table of the database and commits 
        /// </summary>
        /// <param name="pr"></param>
        public void AddProgram(program pr)
        {
            AddProgram(pr, true);
        }

        /// <summary>
        ///     Adds program list into programs table
        /// </summary>
        /// <param name="programs">List of programs to be added</param>        
        public void AddProgramRange(List<program> programs)
        {
            try
            {
                foreach (var item in programs)
                {
                    AddProgram(item, false);
                }
            }
            catch (InvalidArgsException e)
            {
                // TODO rollback changes.
                InvalidArgsException ex = new InvalidArgsException(e.Message + "\nNo items were committed.");
                throw ex;
            }
            
            dataModel.SaveChanges();
        }

        /// <summary>
        ///     Adds category into categories table of database
        /// </summary>
        /// <param name="cat">Category object to be added</param>
        /// <returns>Returns the obtained id of the category</returns>
        public int AddCategory(category cat)
        {
            if (cat == null)
                throw new InvalidArgsException("The parameter is null");

            if (String.IsNullOrEmpty(cat.catName))
                throw new InvalidArgsException("The 'catName' field is null");

            int count = dataModel.categories.Count();
            if (count == 0)
                cat.catID = 1;
            else
            {
                cat.catID = (short)++count;
            }
            
            dataModel.categories.AddObject(cat);
            dataModel.SaveChanges();
            return cat.catID;
        }

        /// <summary>
        ///     Adds language into languages table of database
        /// </summary>
        /// <param name="cat">Language object to be added</param>
        /// <returns>Returns the obtained id of the language</returns>
        public int AddLanguage(language lng)
        {
            if (lng == null)
                throw new InvalidArgsException("The parameter is null");

            if (String.IsNullOrEmpty(lng.languageName))
                throw new InvalidArgsException("The 'languageName' field is null");

            int count = dataModel.languages.Count();
            if (count == 0)
                lng.languageID = 1;
            else
            {
                lng.languageID = (short)++count;
            }

            dataModel.languages.AddObject(lng);
            dataModel.SaveChanges();
            return lng.languageID;
        }

        /// <summary>
        ///     Adds country into countries table of database
        /// </summary>
        /// <param name="cat">Country object to be added</param>
        /// <returns>Returns the obtained id of the country</returns>
        public int AddCountry(country ctr)
        {
            if (ctr == null)
                throw new InvalidArgsException("The parameter is null");

            if (String.IsNullOrEmpty(ctr.countryName))
                throw new InvalidArgsException("The 'countryName' field is null");

            int count = dataModel.languages.Count();
            if (count == 0)
                ctr.countryID = 1;
            else
            {
                ctr.countryID = (short)++count;
            }

            dataModel.countries.AddObject(ctr);
            dataModel.SaveChanges();
            return ctr.countryID;
        }

        /// <summary>
        ///     Gets program list with specified Channel Id and Date
        /// </summary>
        public List<program> GetPrograms(DateTime date, int  ch)
        {
           return  (from p in dataModel.programs
                       where
                           (date.Year == p.date.Year) && (date.Month == p.date.Month) &&
                           (date.Day == p.date.Day) && (ch == p.channelID)
                       select p).ToList<program>();
        }

        /// <summary>
        ///     Gets Channel list 
        /// </summary>
        public List<channel> GetChannels()
        {
            return (from p in dataModel.channels 
                   orderby p.channelID
                    select p).ToList<channel>();
        }

        /// <summary>
        ///     Moves all records from programs table to hprograms table
        /// </summary>
        public void ProgsToHistory()
        {
            List<program> progList = new List<program>();
            foreach (var item in dataModel.programs)
            {
                hprogram pr = new hprogram 
                {
                    categoryID = item.categoryID,
                    channelID = item.channelID,
                    countryID = item.countryID,
                    date = item.date,
                    description = item.description,
                    languageID = item.languageID,
                    title = item.title,
                    hisDate = DateTime.Now.AddHours(Utils.ZoneHour)
                };
                progList.Add(item);                
                dataModel.hprograms.AddObject(pr);
            }
            foreach (var item in progList)
            {
                dataModel.programs.DeleteObject(item);
            }

            dataModel.SaveChanges();            
        }
    }
}
